using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using Magiq.Databases.Insert;
using Magiq.Insert;
using Magiq.Support;
using MirrorMirror;

namespace Magiq.Databases.SqlServer
{
    public class BulkCopyDestination<TSource, TDestination, TNew> : IInsertDestination<TNew>
    {
        private readonly IOrmAdapter ormAdapter;
        private readonly IInsert<TSource, TDestination, TNew> insert;

        public BulkCopyDestination(IInsert<TSource, TDestination, TNew> insert, IOrmAdapter ormAdapter)
        {
            this.insert = insert;
            this.ormAdapter = ormAdapter;
        }

        public int BatchSize { get; set; }
        public int Timeout { get; set; }

        #region IInsertDestination<TNew> Members

        public void Insert(IEnumerable<TNew> items)
        {
            var databaseInserts = ormAdapter.BuildDatabaseInserts(insert);
            foreach (var databaseInsert in databaseInserts)
            {
                var table = new DataTable(databaseInsert.DestinationTypeMapping.TableName);

                var adapters = GetAdapters(databaseInsert);

                foreach (var adapter in adapters)
                {
                    for (var i = 0; i < adapter.Columns.Length; i++)
                    {
                        var column = adapter.Columns[i];
                        table.Columns.Add(column);
                    }
                }

                foreach (var item in items)
                {
                    var row = table.NewRow();

                    foreach (var adapter in adapters)
                        FillRowWith(adapter, item, row);

                    table.Rows.Add(row);
                }

                RunBulkCopy(table);
            }
        }

        #endregion

        private static void FillRowWith(ColumnAssignmentAdapter adapter, TNew item, DataRow row)
        {
            var propertyValue = item.Get(adapter.PropertyName);

            if (adapter.PropertyTypeMapping.IsEntity)
            {
                var ids = adapter.PropertyTypeMapping.Ids.ToArray();

                for (var i = 0; i < ids.Length; i++)
                {
                    var id = ids[i];
                    var column = adapter.Columns[i];

                    var value = propertyValue != null
                                    ? propertyValue.Get(id.PropertyName)
                                    : DBNull.Value;

                    row[column] = NormalizeNull(value);
                }
            }
            else
            {
                row[adapter.Columns.First()] = NormalizeNull(propertyValue);
            }
        }

        private static object NormalizeNull(object value)
        {
            if (value == null)
                return DBNull.Value;

            var type = value.GetType();
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof (Nullable<>))
            {
                return value.Get<bool>("HasValue")
                           ? value.Get("Value")
                           : DBNull.Value;
            }
            return value;
        }

        private List<ColumnAssignmentAdapter> GetAdapters(DatabaseInsert<TSource> databaseInsert)
        {
            var adapters = new List<ColumnAssignmentAdapter>();

            foreach (var assignment in databaseInsert.ColumnAssignments)
            {
                var adapter = new ColumnAssignmentAdapter
                                  {
                                      PropertyTypeMapping = ormAdapter.GetMappingOf(assignment.PropertyType),
                                      Columns = databaseInsert.DestinationTypeMapping
                                          .ColumnsFor(assignment.PropertyName)
                                          .ToArray(),
                                      PropertyName = assignment.PropertyName,
                                  };

                adapters.Add(adapter);
            }
            return adapters;
        }

        private void RunBulkCopy(DataTable table)
        {
            ormAdapter.GetConnection().Run(delegate(IDbConnection x)
                                                    {
                                                        using (var bulkcopy = new SqlBulkCopy((SqlConnection) x))
                                                        {
                                                            foreach (DataColumn column in table.Columns)
                                                                bulkcopy.ColumnMappings.Add(column.ColumnName,
                                                                                            column.ColumnName);

                                                            bulkcopy.BulkCopyTimeout = Timeout;
                                                            bulkcopy.BatchSize = BatchSize;
                                                            bulkcopy.DestinationTableName = table.TableName;
                                                            bulkcopy.WriteToServer(table);
                                                        }
                                                    });
        }

        #region Nested type: ColumnAssignmentAdapter

        private class ColumnAssignmentAdapter
        {
            public string[] Columns { get; set; }
            public ITypeMapping PropertyTypeMapping { get; set; }
            public string PropertyName { get; set; }
        }

        #endregion
    }
}